﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using WcfFlashRemoting.AmfObjects;

namespace WcfFlashRemoting.IO.Writers
{
    internal class Amf3Writer : Amf0Writer
    {
        public Amf3Writer(BinaryWriter writer)
            : base(writer)
        {
        }



        protected override void WriteContent(object p)
        {
            if (p == null)
            {
                writer.Write((byte)Amf0TypeCode.Null);
                return;
            }

            if (p.GetType().IsPrimitive || p is string)
            {
                base.WriteContent(p);
            }
            else
            {
                writer.Write((byte)Amf0TypeCode.Amf3Tag);
                WriteAmf3Content(p);
            }
        }


        private void WriteAmf3Content(object p)
        {
            if (p == null)
            {
                writer.Write((byte)Amf3TypeCode.Null);
                return;
            }

            if (p.GetType().IsPrimitive || p is string || p is DateTime)
            {
                WriteAmf3Primitive(p);
            }
            else if (p.GetType().IsEnum)
            {
                WriteAmf3Primitive(p.ToString());
            }
            else if (p is System.Xml.XmlNode)
            {
                writer.Write((byte)Amf3TypeCode.Xml);
                WriteAmf3UTF((p as System.Xml.XmlNode).OuterXml);
            }
            else if (p.GetType().IsArray)
            {
                writer.Write((byte)Amf3TypeCode.Array);
                int length = (p as Array).Length;
                length = (length << 1) | 1;
                WriteAmf3Integer(length);
                WriteAmf3UTF(string.Empty);
                for (int i = 0; i < (p as Array).Length; i++)
                {
                    WriteAmf3Content((p as Array).GetValue(i));
                }
            }
            else if (p is NamedDictionary)
            {
                NamedDictionary nd = p as NamedDictionary;
                writer.Write((byte)Amf3TypeCode.Object);
                int memberCount = (nd.Properties.Count << 4) | 3;
                WriteAmf3Integer(memberCount);
                WriteAmf3UTF(nd.TypeName ?? "null");
                foreach (KeyValuePair<string, object> kvp in nd.Properties)
                {
                    WriteAmf3UTF(kvp.Key);
                }
                foreach (KeyValuePair<string, object> kvp in nd.Properties)
                {
                    WriteAmf3Content(kvp.Value);
                }
            }
            else
            {
                WriteAmf3Content(GetNamedDictionaryFor(p));
            }
        }

        private object GetNamedDictionaryFor(object p)
        {
            return WcfFlashRemoting.ServiceModel.TypeHelper.GetNamedDictionaryFor(p);
        }

        private void WriteAmf3Primitive(object p)
        {
            switch (Type.GetTypeCode(p.GetType()))
            {
                case TypeCode.Boolean:
                    if (Convert.ToBoolean(p) == true)
                    {
                        writer.Write((byte)Amf3TypeCode.BooleanTrue);
                    }
                    else
                    {
                        writer.Write((byte)Amf3TypeCode.BooleanFalse);
                    }
                    return;
                case TypeCode.Char:
                case TypeCode.Int64:
                case TypeCode.UInt64:
                    writer.Write((byte)Amf3TypeCode.String);
                    WriteAmf3UTF(p.ToString());
                    return;
                case TypeCode.SByte:
                case TypeCode.Byte:
                case TypeCode.Int16:
                case TypeCode.UInt16:
                case TypeCode.Int32:
                case TypeCode.UInt32:
                    writer.Write((byte)Amf3TypeCode.Integer);
                    WriteAmf3Integer(Convert.ToInt32(p));
                    return;
                case TypeCode.Single:
                case TypeCode.Double:
                case TypeCode.Decimal:
                    writer.Write((byte)Amf3TypeCode.Number);
                    writer.Write(Convert.ToDouble(p));
                    return;
                case TypeCode.DateTime:
                    writer.Write((byte)Amf3TypeCode.DateTime);
                    WriteAmf3DateTime((DateTime)p);
                    return;
                case TypeCode.String:
                    writer.Write((byte)Amf3TypeCode.String);
                    WriteAmf3UTF(p as string);
                    return;
            }
            throw new InvalidOperationException();
        }

        #region Amf Type Writers

        private void WriteAmf3DateTime(DateTime dateTime)
        {
            WriteAmf3Integer(1);
            DateTime timeStart = new DateTime(1970, 1, 1, 0, 0, 0);
            TimeSpan span = dateTime.ToUniversalTime().Subtract(timeStart);
            long milliSeconds = (long)span.TotalMilliseconds;
            writer.Write((double)milliSeconds);
        }

        #endregion

        #region Primitive Writers

        private void WriteAmf3Integer(int value)
        {
            //Sign contraction - the high order bit of the resulting value must match every bit removed from the number
            //Clear 3 bits 
            value &= 0x1fffffff;
            if (value < 0x80)
            {
                writer.Write((byte)value);
            }
            else if (value < 0x4000)
            {
                writer.Write((byte)(value >> 7 & 0x7f | 0x80));
                writer.Write((byte)(value & 0x7f));
            }
            else if (value < 0x200000)
            {
                writer.Write((byte)(value >> 14 & 0x7f | 0x80));
                writer.Write((byte)(value >> 7 & 0x7f | 0x80));
                writer.Write((byte)(value & 0x7f));
            }
            else
            {
                writer.Write((byte)(value >> 22 & 0x7f | 0x80));
                writer.Write((byte)(value >> 15 & 0x7f | 0x80));
                writer.Write((byte)(value >> 8 & 0x7f | 0x80));
                writer.Write((byte)(value & 0xff));
            }
        }

        private void WriteAmf3UTF(string value)
        {
            if (value == string.Empty)
            {
                WriteAmf3Integer(1);
            }
            else
            {
                UTF8Encoding utf8Encoding = new UTF8Encoding();
                int byteCount = utf8Encoding.GetByteCount(value);
                int handle = byteCount;
                handle = handle << 1;
                handle = handle | 1;
                WriteAmf3Integer(handle);
                byte[] buffer = utf8Encoding.GetBytes(value);
                if (buffer.Length > 0)
                    writer.Write(buffer);
            }
        }

        #endregion

    }
}
